/*	--- Servlet入门 --- 


在上一节中，我们看到，编写HTTP服务器其实是非常简单的，只需要先编写基于多线程的TCP服务，然后在一个TCP连接中读取HTTP请求，发送HTTP响应即可。

但是，要编写一个【完善的HTTP服务器】，以【HTTP/1.1】为例，【需要考虑】的包括：

	· 识别正确和错误的HTTP请求；
	· 识别正确和错误的HTTP头；
	· 复用TCP连接；
	· 复用线程；
	· IO异常处理；
	...


这些【基础工作】需要耗费【大量的时间】，并且经过【长期测试】才能【稳定运行】。

如果我们只需要输出【一个简单的HTML页面】，就不得不【编写上千行底层代码】，那就根本无法做到【高效而可靠地开发】。

因此，在【JavaEE平台】上，处理【TCP连接】，【解析HTTP协议】这些【底层工作】统统扔给【现成的Web服务器】去做，我们只需要把【自己的应用程序】跑在【Web服务器】上。

为了实现这一目的，【JavaEE】提供了【Servlet API】，我们使用【Servlet API】编写【自己的Servlet】来处理【HTTP请求】，【Web服务器】实现【Servlet API接口】，实现【底层功能】：

                 ┌───────────┐
                 │My Servlet │
                 ├───────────┤
                 │Servlet API│
┌───────┐  HTTP  ├───────────┤
│Browser│<──────>│Web Server │
└───────┘        └───────────┘


我们来实现一个最简单的Servlet： */
// WebServlet注解 表示 这是一个Servlet,并映射到地址
@WebServlet(urlPatterns = "/")
public class HelloServlet extends HttpServlet {

	protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		// 设置响应类型
		resp.setContentType("text/html");

		// 获取输出流
		PrintWriter pw = resp.getWriter();

		// 写入响应
		pw.write("<h1>Hello, world!</h1>");

		// 最后不要忘记flush强制输出
		pw.flush();
	}
}

/*
一个【Servlet】总是继承于【HttpServlet】，然后【覆写doGet()或doPost()方法】。

注意到【doGet()方法】传入了【HttpServletRequest和HttpServletResponse两个对象】，分别代表【HTTP请求】和【HTTP响应】。

我们【使用Servlet API】时，并【不直接】与【底层TCP】交互，也不需要解析HTTP协议，因为【HttpServletRequest和HttpServletResponse】就已经封装好了【请求和响应】。

以【发送响应】为例，我们只需要设置【正确的响应类型】，然后【获取PrintWriter】，【写入响应】即可。


现在问题来了：
	Servlet API是谁提供？

【Servlet API】是一个【jar包】，我们需要通过【Maven】来引入它，才能【正常编译】。

编写【pom.xml文件】如下： 

<project xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.itranswarp.learnjava</groupId>
    <artifactId>web-servlet-hello</artifactId>
    <packaging>war</packaging>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <maven.compiler.source>11</maven.compiler.source>
        <maven.compiler.target>11</maven.compiler.target>
        <java.version>11</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>4.0.0</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>

    <build>
        <finalName>hello</finalName>
    </build>
</project>



注意到这个【pom.xml】与前面我们讲到的【普通Java程序】有个区别，【打包类型】不是【jar】，而是【war】，表示【Java Web Application Archive】： 

<packaging>war</packaging>



引入的Servlet API如下： 

<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>javax.servlet-api</artifactId>
    <version>4.0.0</version>
    <scope>provided</scope>
</dependency>


注意到【 <scope>指定为provided 】，表示【编译时使用】，但【不会打包到.war文件】中，因为【运行期のWeb服务器】本身已经提供了【Servlet API相关的jar包】。

我们还需要在工程目录下创建一个【web.xml描述文件】，放到【src/main/webapp/WEB-INF】目录下（固定目录结构，不要修改路径，注意大小写）。

【文件内容】可以固定如下： 

<!DOCTYPE web-app PUBLIC
 "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
 "http://java.sun.com/dtd/web-app_2_3.dtd">
<web-app>
  <display-name>Archetype Created Web Application</display-name>
</web-app>




整个工程结构如下：

web-servlet-hello
├── pom.xml
└── src
    └── main
        ├── java
        │   └── com
        │       └── itranswarp
        │           └── learnjava
        │               └── servlet
        │                   └── HelloServlet.java
        ├── resources
        └── webapp
            └── WEB-INF
                └── web.xml


【运行Maven】命令【mvn clean package】，在【target目录】下得到一个【hello.war文件】，【这个hello.war文件】就是我们【编译打包后的Web应用程序】。

---------------------------

现在问题又来了：我们应该如何【运行这个.war文件】？

【普通的Java程序】是通过【启动JVM】，然后【执行main()方法】开始运行。

但是【Web应用程序】有所不同，我们【无法直接运行war文件】，必须【先启动Web服务器】，再由【Web服务器】加载【我们编写的HelloServlet】，这样就可以让【HelloServlet】处理【浏览器发送的请求】。

因此，我们首先要找一个【支持Servlet API的Web服务器】。

常用的服务器有：

	· Tomcat： 由Apache开发的开源免费服务器；
	· Jetty： 由Eclipse开发的开源免费服务器；
	· GlassFish： 一个开源的全功能JavaEE服务器。

还有一些【收费的商用服务器】，如【Oracle的WebLogic，IBM的WebSphere】。

无论使用【哪个服务器】，只要它支持【Servlet API 4.0】（因为我们引入的Servlet版本是4.0），我们的【war包】都可以在【上面运行】。

这里我们选择使用【最广泛的开源免费的Tomcat服务器】。

要【运行】我们的【hello.war】，首先要【下载Tomcat服务器】，解压后，把【hello.war】复制到【Tomcat的webapps目录】下，然后切换到【Tomcat的bin目录】，执行【startup.sh】或【startup.bat】启动【Tomcat服务器】：

$ ./startup.sh 
Using CATALINA_BASE:   .../apache-tomcat-9.0.30
Using CATALINA_HOME:   .../apache-tomcat-9.0.30
Using CATALINA_TMPDIR: .../apache-tomcat-9.0.30/temp
Using JRE_HOME:        .../jdk-11.jdk/Contents/Home
Using CLASSPATH:       .../apache-tomcat-9.0.30/bin/bootstrap.jar:...
Tomcat started.


在【浏览器】输入【http://localhost:8080/hello/】即可看到【HelloServlet的输出】：


----------------------------

实际上，类似【Tomcat这样的服务器】也是【Java编写】的，启动【Tomcat服务器】实际上是【启动Java虚拟机】，执行【Tomcat的main()方法】，然后由【Tomcat】负责【加载】我们的【.war文件】，并创建一个【HelloServlet实例】，最后以【多线程的模式】来处理【HTTP请求】。

如果【Tomcat服务器】收到的【请求路径】是【/】（假定部署文件为ROOT.war），就转发到【HelloServlet】并【传入HttpServletRequest和HttpServletResponse两个对象】。

因为我们编写的【Servlet】并【不是直接运行】，而是由【Web服务器】加载后【创建实例运行】，所以，类似【Tomcat这样的Web服务器】也称为【Servlet容器】。

--------------------------

在【Servlet容器】中运行的【Servlet】具有如下【特点】：

	· 【无法在代码中】直接通过【new】创建【Servlet实例】，必须由【Servlet容器, 服务器部署】自动创建【Servlet实例】;
	
	· 【Servlet容器】只会给【每个Servlet类】创建【唯一实例】;
	
	· 【Servlet容器】会使用【多线程】执行【doGet()或doPost()方法】。

--------------------------

复习一下【Java多线程】的内容，我们可以得出【结论】：

	· 在【Servlet】中定义的【实例变量】会被【多个线程】同时访问，要注意【线程安全】；
	
	· 【HttpServletRequest和HttpServletResponse实例】是由【Servlet容器】传入的【局部变量】，它们只能被【当前线程】访问，不存在【多个线程访问的问题】；
	
	· 在【doGet()或doPost()方法】中，如果使用了【ThreadLocal】，但没有清理，那么【它的状态】很可能会影响到【下次的某个请求】，因为【Servlet容器】很可能用【线程池】实现【线程复用】。

因此，正确编写Servlet，要清晰理解【Java的多线程模型】，需要【同步访问】的必须【同步】。



=============================================================


#	练习

给【HelloServlet】增加一个【URL参数】，例如【传入http://localhost:8080/?name=Bob】，能够【输出Hello, Bob!】。 */
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

	resp.setContentType("text/html");
	PrintWriter pw = resp.getWriter();

	String name = req.getParameter("name")
	String world = "world"; // default

	if (name != null && !name.isBlank()) {
		word = name;
	}

	pw.write("<h1>Hello, " + word + "!</h1>");
	pw.flush();
}



/*=============================================================


#	----- 小结 -----

1. 编写【Web应用程序】就是【编写Servlet】处理【HTTP请求】；

2. 【Servlet API】提供了【HttpServletRequest和HttpServletResponse两个高级接口】来封装【HTTP请求、HTTP响应】；

3. 【Web应用程序】必须按【固定结构组织】并打包为【.war文件】；

4. 需要启动【Web服务器】来【加载】我们的【war包】来【运行Servlet】。


===============================

各个Web服务器的部署安装 の URL

-- 开源免费
Tomcat: https://tomcat.apache.org/
Jetty: 	https://www.eclipse.org/jetty/
GlassFish:	https://javaee.github.io/glassfish/

-- 商用
Oracle WebLogic: https://www.oracle.com/middleware/weblogic/
IBM WebSphere:	 https://www.ibm.com/cloud/websphere-application-platform/

*/
































